javascript 您所在的位置:网站首页 vue 异步更新原理 javascript

javascript

2023-03-29 17:13| 来源: 网络整理| 查看: 265

异步组件为什么需要异步组件Vue 作为单页面应用在加载首页时常会遇到加载缓慢的问题,导致在使用体验较差,这是因为在打包单页面应用时,页面会吧脚本打包成一个文件,这个文件包含了所有业务和非业务的代码, 脚本文件过大导致渲染页面时缓慢。在进行首屏性能优化时,最常用的方法就是对于文件的拆分和代理的分离,按需加载的概念也是在这个前提下引入的。因此在 Vue 开发过程中,我们会把一些非首屏的组件设计成异部组件,部分不影响初次视觉体验的组件也可以设计成异步组件。定义异步组件

在 Vue 中,在注册组件时使用一个工厂函数定义组件,这个工厂函数会异步解析组件定义,只有当这个组件需要被渲染时才会触发该工厂函数,并且会把结果缓存起来以便后续使用。

// 注册全局组件时,定义为异步组件 Vue.component("async-component", (resolve, reject)=>{ setTimeout(()=>{ // 使用一个定时器来模拟异步加载过程 // resolve 需要返回一个组件的定义对象,该对象的属性与定义普通组件的属性一致 resolve({ template: "async-component" }) }, 2000) }) // 注册局部异步组件 const vm = new Vue({ components:{ asyncComponent: ()=> import('./test.vue') } })异步组件流程分析

在组件基础的分析过程中,我们分析了实例的挂载流程分为根据渲染函数创建 Vnode 和根据 Vnode 创建真实 DOM 的过程。在创建 Vnode 的过程中,如果遇到组件占位符,会调用 createComponent ,在该方法中,会为子组件做选项合并和安装钩子函数。异步组件的处理也是在该函数中进行的。

function createComponent ( Ctor: Class | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array, tag?: string ): VNode | Array | void { if (isUndef(Ctor)) { return } /** * 这里的 baseCtor 为 Vue 构造函数 */ const baseCtor = context.$options._base // async component // 异步组件 let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {} // 合并构造器配置 resolveConstructorOptions(Ctor) // 安装组件钩子函数 installComponentHooks(data) const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) return vnode }

在注册异步组件时, Vue.component(name, options) 第二个参数工厂函数不是一个普通对象,因此无论是全局注册还是局部注册, 都不会执行 Vue.extend 方法生成子类构造器,所以在上面的 createComponent 方法中, Ctor.cid 不会存在,代码进入异步组件的分支。

异步组件的核心是 resolveAsyncComponent 函数,我们主要关心工厂函数的处理部分,来看下精简后的代码

function resolveAsyncComponent ( factory: Function, baseCtor: Class ): Class | void { if (owner && !isDef(factory.owners)) { const owners = factory.owners = [owner] let sync = true let timerLoading = null let timerTimeout = null ;(owner: any).$on('hook:destroyed', () => remove(owners, owner)) const resolve = once((res: Object | Class) => {}) const reject = once(reason => {}) const res = factory(resolve, reject) // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved } }

根据上面的代码,针对异步组件工厂函数的写法,我们可以总结成三个步骤:

定义异步请求成功和请求失败的处理函数执行组件定义的工厂函数同步返回请求成功的函数处理

resolve , reject 函数都是 once 方法执行的结果, once 方法的作用是防止多次调用异步组件, 使得 resolve , reject 只会执行一次。

function once (fn: Function): Function { // 利用闭包的特性将 called 作为标志位 let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } } }

来看下 resolve , reject 的处理逻辑

const forceRender = (renderCompleted: boolean) => { for (let i = 0, l = owners.length; i < l; i++) { (owners[i]: any).$forceUpdate() } if (renderCompleted) { owners.length = 0 if (timerLoading !== null) { clearTimeout(timerLoading) timerLoading = null } if (timerTimeout !== null) { clearTimeout(timerTimeout) timerTimeout = null } } } const resolve = once((res: Object | Class) => { // 专程组件构造器,并缓存到 resolved 属性中 factory.resolved = ensureCtor(res, baseCtor) if (!sync) { // 强制刷新视图 forceRender(true) } else { owners.length = 0 } }) // 组件加载失败处理函数 const reject = once(reason => { process.env.NODE_ENV !== 'production' && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) if (isDef(factory.errorComp)) { factory.error = true forceRender(true) } })

组件构造器创建完成之后,会进行一次视图的重新渲染。 由于 Vue 是数据驱动试图进行渲染的,则组件在加载完毕之后,并没有发生数据的变化,因此需要手动强制更新试图。 forceRender 函数内部会拿到每个调用异步组件的实例,然后执行 Vue 原型上的 $forceUpdate 方法刷新视图。 在异步组件加载过程中,因为 Ctor 为 undefine 会同步创建一个注释节点,在异步组件加载完成之后,触发 $forceUpdate 再次执行 createEmptyVNode , 这是 Ctor 不为 undefined ,因此会走正常的组件渲染流程

Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) }Promise 异步组件

异步组件的另一种写法是在工厂函数中返回一个 Promise 对象,在 ES6 中引入了 import 来加载模块, import 是一个运行时加载模块的方法,,可以和 require 进行对比, import 是异步加载的, require 是同步加载的,并且 import 会返回一个 Promise 对象

具体用法Vue.component("asyncComponent", ()=> import('./test.vue'))

清楚 Promise 异步组件的注册方式之后,继续来分析异步的流程。

const res = factory(resolve, reject) if (isObject(res)) { if (isPromise(res)) { if (isUndef(factory.resolved)) { res.then(resolve, reject) } } }

在工厂函数内部使用 import 引入一个异步组件时, 工厂函数回返回一个 Promise 对象,成功加载则执行 resolve , 失败则执行 reject 。 其中判断一个对象是否为 Promise 对象最简单的方法就是是否存在 then 和 catch 方法

function isPromise (val: any): boolean { return ( isDef(val) && typeof val.then === 'function' && typeof val.catch === 'function' ) }高级异步组件

为了在使用时能有更好的体验,可以在加载异步组件的过程中使用 loading 组件来显示一个等待状态,使用 error 组件处理组件加载失败的错误提示等。在定义异步组件时,工厂函数可以返回一个对象,对象中可以定义需要加载的组件 component , 加载过程中显示的 loading 组件,加载错误显示的 error 组件。在组件渲染过程中,同样进入异步组件的分支

Vue.component("asyncComponent", ()=> { // 需要加载的组件 component: import('./test.vue'), // 加载过程中显示的 loading 组件 loading: LoadingComponent, // 组件加载错误时显示的组件 error: ErrorComponent, // loading 组件显示的延迟时间,默认为 200, 如果 delay 时间后,组件还没有加载成功,则显示 loading 组件 delay: 200, // 组件加载超时时间,超过该时间组件为加载成功,认为组件加载失败,使用 error 组件进行提示 timeout: 3000 })

对于高级异步组件,工厂函数返回的是一个对象,来看下对于高级异步组件 Vue 的处理过程

if (isObject(res)) { if (isPromise(res)) { // 工厂函数返回一个 Promise 的处理逻辑 } else if (isPromise(res.component)) { // 高级异步组件的处理流程 // 组件加载过程了 Promise 异步组件处理方式相同 res.component.then(resolve, reject) if (isDef(res.error)) { // 定义了错误组件时,创建错误组件的子类构造器,并保存到 errorComp 中 factory.errorComp = ensureCtor(res.error, baseCtor) } if (isDef(res.loading)) { // 创建加载时组件子类构造器,并保存到 loadingComp zhong factory.loadingComp = ensureCtor(res.loading, baseCtor) if (res.delay === 0) { // 立即展现加载时组件 factory.loading = true } else { timerLoading = setTimeout(() => { timerLoading = null // 延时指定时间后,组件还没加载完成并没有加载失败,显示加载时组件 if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true forceRender(false) } // 默认显示加载时组件延时时间为 200 ms }, res.delay || 200) } } if (isDef(res.timeout)) { timerTimeout = setTimeout(() => { timerTimeout = null if (isUndef(factory.resolved)) { // 规定时间内异步组件没有加载成功,触发加载失败 reject( process.env.NODE_ENV !== 'production' ? `timeout (${res.timeout}ms)` : null ) } }, res.timeout) } } }

通过分析上方代码可以看出,高级组件的加载过程 Promise 组件的加载过程相同,额外添加了加载失败和加载过程中的处理逻辑。通过 delay 属性来延迟显示加载中组件的显示,通过 timeout 属性来规定超时时间。

webpack 异步组件用法

在使用 webpack 打包 Vue 应用时,我们可以将异步组件的代码进行分离。 webpack 为异步组件的加载提供了两种写法

require.ensure(): 这是 webpack 提供给异步组件的写法, webpack 在打包时, 会静态地解析代码中的 require.ensure() , 同时将模块添加到一个分开的 chunk ,中,其中函数的第三个参数为分离代码块的名称Vue.component("asyncComponent", (resolve, reject)=>{ require.ensure([], ()=>{ resolve(require('./test.vue')) }, "asyncComponent") // 这里的 asyncComponent 为 chunkName })import(/* webpackChunkName: "chunkName" */, component): 在 ES6 , import 方法是推荐的写法, 通过注释 webpackChunkName 来指定分离后组件模块的命名Vue.component('asyncComponent', () => import(/* webpackChunkName: "asyncComponent" */, './test.vue'))


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有